package com.hearsilent.discreteslider.libs;

import ohos.agp.utils.Point;
import ohos.app.Context;
import ohos.multimodalinput.event.TouchEvent;

/**
 * @author Almer Thie (code.almeros.com)
 * Copyright (c) 2013, Almer Thie (code.almeros.com)
 * <p>
 * All rights reserved.
 * <p>
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * <p>
 * Redistributions of source code must retain the above
 * copyright notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the distribution.
 * <p>
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 */
public class MoveGestureDetector extends BaseGestureDetector {
    /**
     * Listener which must be implemented which is used by MoveGestureDetector
     * to perform callbacks to any implementing class which is registered to a
     * MoveGestureDetector via the constructor.
     *
     * @see SimpleOnMoveGestureListener
     */
    public interface OnMoveGestureListener {
        boolean onMove(MoveGestureDetector detector);

        boolean onMoveBegin(MoveGestureDetector detector);

        void onMoveEnd(MoveGestureDetector detector);
    }

    /**
     * Helper class which may be extended and where the methods may be
     * implemented. This way it is not necessary to implement all methods
     * of OnMoveGestureListener.
     */
    public static class SimpleOnMoveGestureListener implements OnMoveGestureListener {
        /**
         * onMove
         *
         * @param detector detector
         * @return false
         */
        public boolean onMove(MoveGestureDetector detector) {
            return false;
        }

        /**
         * onMoveBegin
         *
         * @param detector detector
         * @return true
         */
        public boolean onMoveBegin(MoveGestureDetector detector) {
            return true;
        }

        /**
         * MoveEnd
         *
         * @param detector detector
         */
        public void onMoveEnd(MoveGestureDetector detector) {
            // Do nothing, overridden implementation may be used
        }
    }

    private static final Point FOCUS_DELTA_ZERO = new Point();

    private final OnMoveGestureListener mListener;

    private Point mCurrFocusInternal;
    private Point mPrevFocusInternal;
    private Point mFocusExternal = new Point();
    private Point mFocusDeltaExternal = new Point();

    public MoveGestureDetector(Context context, OnMoveGestureListener listener) {
        super(context);
        mListener = listener;
    }

    @Override
    protected void handleStartProgressEvent(int actionCode, TouchEvent event) {
        switch (actionCode) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                resetState(); // In case we missed an UP/CANCEL event
                mTimeDelta = 0;

                updateStateByEvent(event);
                break;

            case TouchEvent.POINT_MOVE:
                mGestureInProgress = mListener.onMoveBegin(this);
                break;
        }
    }

    @Override
    protected void handleInProgressEvent(int actionCode, TouchEvent event) {
        switch (actionCode) {
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                mListener.onMoveEnd(this);
                resetState();
                break;

            case TouchEvent.POINT_MOVE:
                // If the gesture started before this detector was attached (somehow),
                // mPrevEvent will be null at this point and BaseGestureDetector's
                // updateStateByEvent() will crash. The following check will prevent this.
                if (mPrevEvent == null) {
                    return;
                }
                updateStateByEvent(event);

                // Only accept the event if our relative pressure is within
                // a certain limit. This can help filter shaky data as a
                // finger is lifted.
                if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
                    final boolean updatePrevious = mListener.onMove(this);
                }
                break;
        }
    }

    /**
     * updateStateByEvent
     *
     * @param curr TouchEvent
     */
    protected void updateStateByEvent(TouchEvent curr) {
        super.updateStateByEvent(curr);
        final TouchEvent prev = mPrevEvent;

        // Focus intenal
        mCurrFocusInternal = determineFocalPoint(curr);
        mPrevFocusInternal = determineFocalPoint(prev);

        // Focus external
        // - Prevent skipping of focus delta when a finger is added or removed
        boolean mSkipNextMoveEvent = prev.getPointerCount() != curr.getPointerCount();
        mFocusDeltaExternal = mSkipNextMoveEvent ? FOCUS_DELTA_ZERO :
                new Point(mCurrFocusInternal.getPointX() - mPrevFocusInternal.getPointX(),
                        mCurrFocusInternal.getPointY() - mPrevFocusInternal.getPointY());
    }

    /**
     * Determine (multi)finger focal point (a.k.a. center point between all
     * fingers)
     *
     * @param es MotionEvent
     * @return PointF focal point
     */
    private Point determineFocalPoint(TouchEvent es) {
        // Number of fingers on screen
        final int pCount = es.getPointerCount();
        float xs = 0f;
        float ys = 0f;

        for (int i = 0; i < pCount; i++) {
            xs += es.getPointerScreenPosition(i).getX();
            ys += es.getPointerScreenPosition(i).getY();
        }

        return new Point(xs / pCount, ys / pCount);
    }

    /**
     * 获取 FocusX
     *
     * @return FocusX
     */
    public float getFocusX() {
        return mFocusExternal.getPointX();
    }

    /**
     * 获取 FocusY
     *
     * @return FocusY
     */
    public float getFocusY() {
        return mFocusExternal.getPointY();
    }

    /**
     * 获取FocusDelta
     *
     * @return Point
     */
    public Point getFocusDelta() {
        return mFocusDeltaExternal;
    }
}